package com.gyp.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gyp.config.idwork.IdContextHolder;
import com.gyp.config.properties.ObsConfig;
import com.gyp.config.properties.WxChatConfig;
import com.gyp.constant.Constants;
import com.gyp.constant.RedisKeyConstant;
import com.gyp.constant.UserConstants;
import com.gyp.dto.user.*;
import com.gyp.entity.SysMenu;
import com.gyp.entity.SysRole;
import com.gyp.entity.SysUserEntity;
import com.gyp.exception.BusinessException;
import com.gyp.mapper.user.SysUserMapper;
import com.gyp.model.LanguageContextHolder;
import com.gyp.model.PageHandler;
import com.gyp.model.UserContextHolder;
import com.gyp.model.UserTokenCache;
import com.gyp.model.common.BusinessCode;
import com.gyp.model.common.PageInfo;
import com.gyp.rpc.WeChatClient;
import com.gyp.rpc.request.GetTokenReq;
import com.gyp.rpc.request.GetUserInfoReq;
import com.gyp.rpc.response.AccessTokenResponse;
import com.gyp.rpc.response.UserInfoResponse;
import com.gyp.service.GyPayOrderService;
import com.gyp.service.UserService;
import com.gyp.utils.*;
import com.gyp.vo.base.MetaVo;
import com.gyp.vo.base.RouterVo;
import com.gyp.vo.base.SysDictData;
import com.gyp.vo.base.UserInfoVO;
import com.gyp.vo.login.LoginUserDetail;
import com.gyp.vo.login.Oauth2TokenVO;
import com.obs.services.ObsClient;
import com.obs.services.model.HttpMethodEnum;
import com.obs.services.model.PutObjectResult;
import com.obs.services.model.TemporarySignatureRequest;
import com.obs.services.model.TemporarySignatureResponse;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import sun.net.www.protocol.http.HttpURLConnection;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.concurrent.TimeUnit;


/**
 * @param
 * @author xia
 * @Description:
 * @date 2021/1/24
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUserEntity> implements UserService {

    @Autowired
    private SysUserMapper userMapper;

    @Autowired
    private WxChatConfig wxChatConfig;

    @Autowired
    private GyPayOrderService payOrderService;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private WeChatClient weChatClient;

    @Autowired
    private ObsConfig obsConfig;

    @Value("${wechat.appid}")
    private String appid;

    @Value("${wechat.appSecret}")
    private String appSecret;

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private PasswordEncoder passwordEncoder;


    /**
     * 用户登录
     * LoginDTO loginDTO
     *
     * @return Map
     */


    @Override
    public void logout(String token) {
        String key = String.format(RedisKeyConstant.USER_TOKEN_PREFIX, token);
        RedisUtils.delKey(key);
    }


    @Override
    public UserInfoVO userInfo() {
        Integer userId = UserContextHolder.getUserId();
        SysUserEntity user = userMapper.selectById(userId);
        //查找角色
        List<String> roles = selectRolePermissionByUserId(userId);
        // 权限集合
        List<String> permissions = getMenuPermission(userId);
        UserInfoVO result = new UserInfoVO();
        UserInfoVO.UserInfoDetail detail = new UserInfoVO.UserInfoDetail();
        BeanUtils.copyProperties(user, detail);
        detail.setUserId(user.getId());
        result.setUser(detail);
        result.setRoles(roles);
        result.setPermissions(permissions);
        return result;
    }

    @Override
    public Oauth2TokenVO wxChatLogin(String code) throws Exception {
        //code换取token

        //获取token
        GetTokenReq req = new GetTokenReq();
        req.setAppid(wxChatConfig.getAppid());
        req.setSecret(wxChatConfig.getAppSecret());
        req.setCode(code);
        AccessTokenResponse res = weChatClient.getToken(req);
        SysUserEntity user = lambdaQuery().eq(SysUserEntity::getOpenid, res.getOpenid()).one();
        if (Objects.nonNull(user)) {
            return createToken(user);
        }
        String accessToken = res.getAccessToken();
        //通过token换取用户信息
        GetUserInfoReq userInfoReq = new GetUserInfoReq();
        userInfoReq.setOpenid(res.getOpenid());
        userInfoReq.setAccessToken(accessToken);
        UserInfoResponse userInfoResponse = weChatClient.getUserInfo(userInfoReq);


        return createToken(user);
    }

    @Override
    public String getWeChatCode() {
        String sceneId = String.valueOf(IdContextHolder.nextId());
        String accessToken = getToken();
        String qrCode = createTempQrCode(accessToken, sceneId);
        return qrCode;
    }

    @SneakyThrows
    @Override
    public void userRegister(RegisterDTO param) {
        Long count = this.lambdaQuery()
                .eq(SysUserEntity::getNickname, param.getPhone())
                .count();
        if (count > 0) {
            throw new BusinessException(BusinessCode.PHONE_EXIST.getCode());
        }
        SysUserEntity user = new SysUserEntity();
        user.setNickname(param.getPhone());
        user.setVoiceTime(0);
        user.setPassword(param.getPassword());
        user.setPasswordEncry(passwordEncoder.encode(param.getPassword()));
        Map<String, String> map = RSAUtil.createKeyPairs(1024);
        String publicKey = map.get("pubKey");
        String privateKey = map.get("privKey");
        user.setPublicKey(publicKey);
        user.setPrivateKey(privateKey);
        save(user);
    }

    @Override
    public Oauth2TokenVO login(LoginDTO param) {
        String phone = param.getPhone();
        String password = param.getPassword();
        UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(
                        phone,
                        password);
        Authentication authenticate = null;
        try {
            authenticate = authenticationManager.authenticate(authentication);
        } catch (BadCredentialsException e) {
            throw new BusinessException(BusinessCode.USER_PASSWORD_ERROR.getCode());
        } catch (DisabledException e) {
            throw new BusinessException(BusinessCode.USER_DISABLED.getCode());
        }
        if (Objects.isNull(authenticate)) {
            throw new BusinessException(BusinessCode.USER_PASSWORD_ERROR.getCode());
        }
        LoginUserDetail loginUser = (LoginUserDetail) authenticate.getPrincipal();
        // 认证成功
        return createToken(loginUser.getSysUserEntity());
    }

    @Override
    public String getUserToken() {
        Boolean status = payOrderService.checkOrder();
        if(!status){
            throw new BusinessException("10047");
        }
        return getById(UserContextHolder.getUserId()).getPublicKey();
    }

    @Override
    public String upload(MultipartFile file) {
        ObsClient obsClient = null;
        try {
            String endPoint = obsConfig.getEndPoint();
            String ak = obsConfig.getAccessKey();
            String sk = obsConfig.getSecretKey();
            // 创建ObsClient实例
            obsClient = new ObsClient(ak, sk, endPoint);
            // 获取文件流
            InputStream inputStream = file.getInputStream();
            String originalFilename = file.getOriginalFilename();
            // 根据年月日创建一个文件夹，方便管理图片信息
            String fileName = new DateTime().toString("yyyy/MM/dd") + UUID.randomUUID().toString().replace("-", "") + originalFilename;
            // 使用文件流的方式上传 -- 创建PutObject请求
            PutObjectResult result = obsClient.putObject(obsConfig.getBucketName(), fileName, inputStream);
            return result.getObjectUrl();
        } catch (Exception e) {
            log.info("文件上传异常:{}", e);
            throw new BusinessException("10056");
        } finally {
            if (obsClient != null) {
                try {
                    obsClient.close(); // 关闭资源
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    public String getSignUrl(String objectKey) {
        // URL有效期，3600秒
        long expireSeconds = 3600L * 10;
        ObsClient obsClient = null;
        String endPoint = obsConfig.getEndPoint();
        String ak = obsConfig.getAccessKey();
        String sk = obsConfig.getSecretKey();
        // 创建ObsClient实例
        obsClient = new ObsClient(ak, sk, endPoint);

        TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expireSeconds);
        request.setBucketName(obsConfig.getBucketName());
        request.setObjectKey(objectKey);

        TemporarySignatureResponse response = obsClient.createTemporarySignature(request);
        log.info("签名地址:{}", response.getSignedUrl());
        return response.getSignedUrl();
    }


    /**
     * 获取access_token
     *
     * @return
     */
    public String createAccessToken() {
        String url = "https://api.weixin.qq.com/cgi-bin/token";
        Map<String, Object> data = new HashMap<>();
        data.put("grant_type", "client_credential");
        data.put("appid", appid);
        data.put("secret", appSecret);
        log.info("createAccessToken data:{}", data);
        String json = HttpUtil.createGet(url).form(data).execute().body();
        String access_token = (String) JSONUtil.getByPath(JSONUtil.parse(json), "access_token");
        return access_token;
    }

    private String getToken() {
        RBucket<String> bucket = redissonClient.getBucket("wechat:token");
        if (StringUtils.isNotBlank(bucket.get())) {
            return bucket.get();
        }
        String token = createAccessToken();
        bucket.set(token, 90, TimeUnit.MINUTES);
        return token;
    }

    /**
     * 创建临时二维码
     *
     * @return
     */
    public String createTempQrCode(String access_token, String scene_str) {
        String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + access_token;
        Map<String, Object> data = new HashMap<>();
        data.put("expire_seconds", 604800);
        data.put("action_name", "QR_STR_SCENE");
        Map<String, Object> action_info = new HashMap<>();
        Map<String, Object> scene = new HashMap<>();
        scene.put("scene_str", scene_str);
        action_info.put("scene", scene);
        data.put("action_info", action_info);
        String json = HttpUtil.createPost(url).header("Content-Type", "application/json").body(JSONUtil.toJsonStr(data)).execute().body();
        System.out.println("json = " + json);
        String qrcode = (String) JSONUtil.getByPath(JSONUtil.parse(json), "url");
        return qrcode;
    }


    private Oauth2TokenVO createToken(SysUserEntity sysUser) {
        String token = JWTTokenUtil.createAccessToken(sysUser.getId());
        UserTokenCache user = new UserTokenCache();
        BeanUtils.copyProperties(sysUser, user);
        user.setUserId(sysUser.getId());
        String key = String.format(RedisKeyConstant.USER_TOKEN_PREFIX, token);
        // 将用户信息存入redis
        LocalDateTime expireTime = LocalDateTime.now().plusDays(15);
        RedisUtils.setValueWithExpire(key, user, TimeUnit.DAYS, 15L);
        Oauth2TokenVO oauth2TokenVO = new Oauth2TokenVO();
        oauth2TokenVO.setAvatar(sysUser.getAvatar());
        oauth2TokenVO.setUserId(sysUser.getId());
        oauth2TokenVO.setNickname(sysUser.getNickname());
        oauth2TokenVO.setVoiceTime(sysUser.getVoiceTime());
        oauth2TokenVO.setExpiresIn(expireTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
        oauth2TokenVO.setToken(token);
        return oauth2TokenVO;
    }

    /**
     * 根据用户ID查询菜单
     *
     * @param userId 用户名称
     * @return 菜单列表
     */
    @Override
    public List<RouterVo> selectMenuTreeByUserId(Integer userId) {
        List<SysMenu> menus = null;

        menus = userMapper.selectMenuTreeByUserId(userId);
        return buildMenus(getChildPerms(menus, 0));
    }


    /**
     * 根据父节点的ID获取所有子节点
     *
     * @param list     分类表
     * @param parentId 传入的父节点ID
     * @return String
     */
    public List<SysMenu> getChildPerms(List<SysMenu> list, int parentId) {
        List<SysMenu> returnList = new ArrayList<SysMenu>();
        for (Iterator<SysMenu> iterator = list.iterator(); iterator.hasNext(); ) {
            SysMenu t = (SysMenu) iterator.next();
            // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点
            if (t.getParentId() == parentId) {
                recursionFn(list, t);
                returnList.add(t);
            }
        }
        return returnList;
    }

    /**
     * 递归列表
     *
     * @param list 分类表
     * @param t    子节点
     */
    private void recursionFn(List<SysMenu> list, SysMenu t) {
        // 得到子节点列表
        List<SysMenu> childList = getChildList(list, t);
        t.setChildren(childList);
        for (SysMenu tChild : childList) {
            if (hasChild(list, tChild)) {
                recursionFn(list, tChild);
            }
        }
    }

    /**
     * 得到子节点列表
     */
    private List<SysMenu> getChildList(List<SysMenu> list, SysMenu t) {
        List<SysMenu> tlist = new ArrayList<SysMenu>();
        Iterator<SysMenu> it = list.iterator();
        while (it.hasNext()) {
            SysMenu n = (SysMenu) it.next();
            if (n.getParentId().longValue() == t.getMenuId().longValue()) {
                tlist.add(n);
            }
        }
        return tlist;
    }

    /**
     * 判断是否有子节点
     */
    private boolean hasChild(List<SysMenu> list, SysMenu t) {
        return getChildList(list, t).size() > 0;
    }

    public List<RouterVo> buildMenus(List<SysMenu> menus) {
        List<RouterVo> routers = new LinkedList<RouterVo>();
        for (SysMenu menu : menus) {
            RouterVo router = new RouterVo();
            router.setHidden("1".equals(menu.getVisible()));
            router.setName(getRouteName(menu));
            router.setPath(getRouterPath(menu));
            router.setComponent(getComponent(menu));
            router.setQuery(menu.getQuery());
            router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
            List<SysMenu> cMenus = menu.getChildren();
            if (CollectionUtils.isNotEmpty(cMenus) && UserConstants.TYPE_DIR.equals(menu.getMenuType())) {
                router.setAlwaysShow(true);
                router.setRedirect("noRedirect");
                router.setChildren(buildMenus(cMenus));
            } else if (isMenuFrame(menu)) {
                router.setMeta(null);
                List<RouterVo> childrenList = new ArrayList<RouterVo>();
                RouterVo children = new RouterVo();
                children.setPath(menu.getPath());
                children.setComponent(menu.getComponent());
                children.setName(StringUtils.capitalize(menu.getPath()));
                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
                children.setQuery(menu.getQuery());
                childrenList.add(children);
                router.setChildren(childrenList);
            } else if (menu.getParentId().intValue() == 0 && isInnerLink(menu)) {
                router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
                router.setPath("/");
                List<RouterVo> childrenList = new ArrayList<RouterVo>();
                RouterVo children = new RouterVo();
                String routerPath = innerLinkReplaceEach(menu.getPath());
                children.setPath(routerPath);
                children.setComponent(UserConstants.INNER_LINK);
                children.setName(StringUtils.capitalize(routerPath));
                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath()));
                childrenList.add(children);
                router.setChildren(childrenList);
            }
            routers.add(router);
        }
        return routers;
    }

    /**
     * 获取路由名称
     *
     * @param menu 菜单信息
     * @return 路由名称
     */
    public String getRouteName(SysMenu menu) {
        String routerName = StringUtils.capitalize(menu.getPath());
        // 非外链并且是一级目录（类型为目录）
        if (isMenuFrame(menu)) {
            routerName = StringUtils.EMPTY;
        }
        return routerName;
    }

    /**
     * 获取路由地址
     *
     * @param menu 菜单信息
     * @return 路由地址
     */
    public String getRouterPath(SysMenu menu) {
        String routerPath = menu.getPath();
        // 内链打开外网方式
        if (menu.getParentId().intValue() != 0 && isInnerLink(menu)) {
            routerPath = innerLinkReplaceEach(routerPath);
        }
        // 非外链并且是一级目录（类型为目录）
        if (0 == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType()) && UserConstants.NO_FRAME.equals(menu.getIsFrame())) {
            routerPath = "/" + menu.getPath();
        }
        // 非外链并且是一级目录（类型为菜单）
        else if (isMenuFrame(menu)) {
            routerPath = "/";
        }
        return routerPath;
    }

    /**
     * 内链域名特殊字符替换
     *
     * @return 替换后的内链域名
     */
    public String innerLinkReplaceEach(String path) {
        return StringUtils.replaceEach(path, new String[]{Constants.HTTP, Constants.HTTPS, Constants.WWW, "."}, new String[]{"", "", "", "/"});
    }

    /**
     * 获取组件信息
     *
     * @param menu 菜单信息
     * @return 组件信息
     */
    public String getComponent(SysMenu menu) {
        String component = UserConstants.LAYOUT;
        if (StringUtils.isNotEmpty(menu.getComponent()) && !isMenuFrame(menu)) {
            component = menu.getComponent();
        } else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != 0 && isInnerLink(menu)) {
            component = UserConstants.INNER_LINK;
        } else if (StringUtils.isEmpty(menu.getComponent()) && isParentView(menu)) {
            component = UserConstants.PARENT_VIEW;
        }
        return component;
    }

    /**
     * 是否为菜单内部跳转
     *
     * @param menu 菜单信息
     * @return 结果
     */
    public boolean isMenuFrame(SysMenu menu) {
        return menu.getParentId().intValue() == 0 && UserConstants.TYPE_MENU.equals(menu.getMenuType()) && menu.getIsFrame().equals(UserConstants.NO_FRAME);
    }

    /**
     * 是否为内链组件
     *
     * @param menu 菜单信息
     * @return 结果
     */
    public boolean isInnerLink(SysMenu menu) {
        return menu.getIsFrame().equals(UserConstants.NO_FRAME) && StringUtils.startsWithAny(menu.getPath(), "http", "https");
    }

    /**
     * 是否为parent_view组件
     *
     * @param menu 菜单信息
     * @return 结果
     */
    public boolean isParentView(SysMenu menu) {
        return menu.getParentId().intValue() != 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType());
    }


    @Override
    public List<SysMenu> selectMenuList(SysMenu menu) {
        return userMapper.selectMenuListByUserId(menu, UserContextHolder.getUserId());
    }

    @Override
    public List<SysDictData> selectDictDataByType(String dictType) {
        List<SysDictData> dictDatas = userMapper.selectDictDataByType(dictType);
        return dictDatas;
    }

    @Override
    public PageInfo<SysRole> selectRoleList(RoleQueryDTO role) {
        Page<SysRole> page = userMapper.selectRoleList(new Page<>(role.getPageNum(), role.getPageSize()), role);
        if (page.getTotal() == 0) {
            return PageHandler.returnNullPage(role);
        }
        return PageHandler.returnPage(page.getRecords(), role, page.getTotal());
    }


    public List<String> getMenuPermission(Integer userId) {
        List<Long> roles = userMapper.listRolesById(userId);
        List<String> perms = new ArrayList<>();
        if (!roles.isEmpty() && roles.size() > 1) {
            // 多角色设置permissions属性，以便数据权限匹配权限
            for (Long role : roles) {
                List<String> rolePerms = selectMenuPermsByRoleId(role);
                perms.addAll(rolePerms);
            }
        } else {
            perms.addAll(selectMenuPermsByUserId(userId));
        }
        return perms;
    }

    public Set<String> selectMenuPermsByUserId(Integer userId) {
        List<String> perms = userMapper.selectMenuPermsByUserId(userId);
        Set<String> permsSet = new HashSet<>();
        for (String perm : perms) {
            if (StringUtils.isNotEmpty(perm)) {
                permsSet.addAll(Arrays.asList(perm.trim().split(",")));
            }
        }
        return permsSet;
    }

    public List<String> selectMenuPermsByRoleId(Long roleId) {
        List<String> perms = userMapper.selectMenuPermsByRoleId(roleId);
        List<String> permsSet = new ArrayList<>();
        for (String perm : perms) {
            if (StringUtils.isNotEmpty(perm)) {
                permsSet.addAll(Arrays.asList(perm.trim().split(",")));
            }
        }
        return permsSet;
    }


    public List<String> selectRolePermissionByUserId(Integer userId) {
        List<SysRole> perms = userMapper.selectRolePermissionByUserId(userId);
        List<String> permsSet = new ArrayList<>();
        for (SysRole perm : perms) {
            if (Objects.nonNull(perm)) {
                permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(",")));
            }
        }
        return permsSet;
    }


}
